using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;


public interface IResolveEngine<TModel> where TModel : class
{
  #region Inner Classes
  public interface IHolder
  {
    bool IsHolding();

    void Hold(ResolveContext context);
    void Release();
  }

  public interface IResolver
  {
    void Resolve(ResolveContext context);
  }
  public class ResolveContext
  {
    private TModel m_model;
    public Action<IHolder> pushHolder;
    public Action<IResolver> pushResolver;
    public Action<string> pushRegisteredHolder;
    public Action<string> pushRegisteredResolver;

    public TModel Model => m_model;

    public ResolveContext(TModel model) {
      m_model = model;
    }

    public void EngineOnlySetModel(TModel model) {
      m_model = model;
    }
  }
  #endregion

  public void RegisterHolder(string key, IResolveEngine<TModel>.IHolder holder);
  public void RegisterResolver(string key, IResolveEngine<TModel>.IResolver resolver);
  public void SetModel(TModel model);
  public void TriggerResolve(IResolver resolver);
  public void Tick();
}

public class ResolveEngineImpl<TModel> : IResolveEngine<TModel> where TModel : class
{
  private IResolveEngine<TModel>.ResolveContext m_resolveContext;

  private TModel m_stableFieldModel;

  private Dictionary<string, IResolveEngine<TModel>.IHolder> m_registeredHolders = new();
  private Dictionary<string, IResolveEngine<TModel>.IResolver> m_registeredResolvers = new();

  private Stack<IResolveEngine<TModel>.IHolder> m_holderStack = new();
  private Stack<IResolveEngine<TModel>.IResolver> m_resolverStack = new();

  private IResolveEngine<TModel>.IHolder curHolder;

  private bool isResolving => m_resolveContext != null;

  public Func<IEnumerable<IResolveEngine<TModel>.IResolver>> getListeningResolvers {
    private get;
    set;
  }

  public event Action onStableEvent;

  #region Public Methods
  public void RegisterHolder(string key, IResolveEngine<TModel>.IHolder holder) {
    if (m_registeredHolders.ContainsKey(key))
    {
      Debug.LogError("[RegisterHolder] confict key:　" + key);
      return;
    }

    m_registeredHolders[key] = holder;
  }

  public void RegisterResolver(string key, IResolveEngine<TModel>.IResolver resolver) {
    if (m_registeredResolvers.ContainsKey(key))
    {
      Debug.LogError("[RegisterResolver] confict key:　" + key);
      return;
    }

    m_registeredResolvers[key] = resolver;
  }

  public TModel GetStableModel() {
    return m_stableFieldModel;
  }

  public void SetModel(TModel initModel) {
    m_stableFieldModel = initModel;
  }

  public void TriggerResolve(IResolveEngine<TModel>.IResolver resolver) {
    if (isResolving)
    {
      return;
    }

    m_resolveContext = ConstructResolveContext();
    PushResolver(resolver);
  }

  public void Tick() {
    while (isResolving)
    {
      bool curHolderIsNull = curHolder.IsNullOrComponentNull();
      if (!curHolderIsNull && curHolder.IsHolding())
      {
        // holder still holding
        return;
      }

      // holder not holding
      if (!curHolderIsNull)
      {
        curHolder.Release();
        curHolder = null;
        // just release a holder, if no resolver left , add listening
        if (m_resolverStack.Count == 0 && getListeningResolvers != null)
        {
          PushResolver(getListeningResolvers());
        }
      }
      curHolder = null;

      while (m_holderStack.Count > 0 && curHolder == null)
      {
        var topHolder = m_holderStack.Pop();
        if (!topHolder.IsNullOrComponentNull())
        {
          topHolder.Hold(m_resolveContext);
          curHolder = topHolder;
          // new holder is holding
          return;
        }
      }

      // no anymore holder
      while (m_resolverStack.Count > 0 && m_holderStack.Count == 0)
      {
        var topResolver = m_resolverStack.Pop();
        if (!topResolver.IsNullOrComponentNull())
        {
          topResolver.Resolve(m_resolveContext);
        }
      }

      // no holder & resolver , it's stable
      if (m_holderStack.Count > 0 || m_resolverStack.Count > 0)
      {
        continue;
      } else
      {
        OnStable();
        break;
      }
    }
  }
  #endregion

  #region Internal Methods
  private void PushResolver(IResolveEngine<TModel>.IResolver resolver) {
    if (resolver.IsNullOrComponentNull())
    {
      return;
    }
    m_resolverStack.Push(resolver);
  }

  private void PushResolver(IEnumerable<IResolveEngine<TModel>.IResolver> resolvers) {
    if (resolvers == null)
    {
      return;
    }
    foreach (var resolver in resolvers)
    {
      PushResolver(resolver);
    }
  }

  private void PushHolder(IResolveEngine<TModel>.IHolder holder) {
    if (holder.IsNullOrComponentNull())
    {
      return;
    }
    m_holderStack.Push(holder);
  }

  private void PushRegisteredHolder(string key) {
    if (!m_registeredHolders.ContainsKey(key))
    {
      Debug.LogError("[PushRegisteredHolder] not exist key:　" + key);
    }

    PushHolder(m_registeredHolders[key]);
  }

  private void PushRegisteredResolver(string key) {
    if (!m_registeredResolvers.ContainsKey(key))
    {
      Debug.LogError("[PushRegisteredHolder] not exist key:　" + key);
    }

    PushResolver(m_registeredResolvers[key]);
  }


  private void OnStable() {
    m_stableFieldModel = m_resolveContext.Model;
    m_resolveContext = null;

    if (onStableEvent != null)
    {
      onStableEvent.Invoke();
    }
  }


  private IResolveEngine<TModel>.ResolveContext ConstructResolveContext() {
    var context = new IResolveEngine<TModel>.ResolveContext(m_stableFieldModel);
    context.pushHolder = PushHolder;
    context.pushResolver = PushResolver;
    context.pushRegisteredHolder = PushRegisteredHolder;
    return context;
  }
  #endregion
}
